package graph;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Scanner;

/**
 * kruskal:最小生成树
 * prim 算法是维护节点的集合，而 Kruskal 是维护边的集合。
 * kruscal的思路：
 * 边的权值排序，因为要优先选最小的边加入到生成树里
 * 遍历排序后的边
 * 如果边首尾的两个节点在同一个集合，说明如果连上这条边图中会出现环
 * 如果边首尾的两个节点不在同一个集合，加入到最小生成树，并把两个节点加入同一个集合
 */

/**
 * 一刷二刷思路一样
 */
class Edge {
    int l, r, val;

    Edge(int l, int r, int val) {
        this.l = l;
        this.r = r;
        this.val = val;
    }
}

public class Kruskal {
    private static int n = 10001;
    private static int[] father = new int[n];

    // 并查集初始化
    public static void init() {
        for (int i = 0; i < n; i++) {
            father[i] = i;
        }
    }

    // 并查集的查找操作
    public static int find(int u) {
        if (u == father[u]) return u;
        return father[u] = find(father[u]);
    }

    // 并查集的加入集合
    public static void join(int u, int v) {
        u = find(u);
        v = find(v);
        if (u == v) return;
        father[v] = u;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int v = sc.nextInt();
        int e = sc.nextInt();
        List<Edge> edges = new ArrayList<>();
        int res = 0;
        for (int i = 0; i < e; i++) {
            int x = sc.nextInt();
            int y = sc.nextInt();
            int k = sc.nextInt();
            edges.add(new Edge(x, y, k));
        }

        edges.sort(Comparator.comparing(edge -> edge.val));

        init();
        for (Edge edge : edges) {
            int x = find(edge.l);
            int y = find(edge.r);
            if (x != y) {
                res += edge.val;
                join(x, y);
            }
        }
        System.out.println(res);
    }
}
